# app_gui.py

import tkinter as tk
from tkinter import ttk, messagebox, filedialog, scrolledtext
import os
import platform
import subprocess
import threading
import queue
import webbrowser
import sys
import traceback # 确保导入

try:
    import json_editor
except ImportError:
    messagebox.showerror("错误", "json_editor.py 未找到或无法导入。\n请确保该文件与 app_gui.py 在同一目录下。")

class NewspaperToolApp:
    def __init__(self, master):
        self.master = master
        master.title("报纸信息查询工具")

        window_width = 900
        window_height = 700
        screen_width = master.winfo_screenwidth()
        screen_height = master.winfo_screenheight()
        center_x = int(screen_width / 2 - window_width / 2)
        center_y = int(screen_height / 2 - window_height / 2)
        master.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
        master.minsize(800, 600)

        self.chromedriver_path_var = tk.StringVar()
        self.output_dir_var = tk.StringVar()
        self.query_mode_var = tk.StringVar(value="single")
        self.scraper_process = None
        self.output_queue = queue.Queue()
        self.last_excel_filepath = None
        self._after_id_process_scraper_output = None

        self.menubar = tk.Menu(master)
        master.config(menu=self.menubar)

        self.file_menu = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="文件(F)", menu=self.file_menu)
        self.settings_menu = tk.Menu(self.file_menu, tearoff=0)
        self.file_menu.add_cascade(label="设置(S)", menu=self.settings_menu)
        self.settings_menu.add_command(label="ChromeDriver 路径...", command=self.configure_chromedriver_path)
        self.settings_menu.add_command(label="默认Excel输出目录...", command=self.configure_output_dir)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="退出(X)", command=self.quit_app)

        self.help_menu = tk.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="帮助(H)", menu=self.help_menu)
        self.help_menu.add_command(label="关于(A)", command=self.show_about_dialog)

        self.status_bar_frame = ttk.Frame(master, relief=tk.SUNKEN, padding="2 2 2 2")
        self.status_bar_frame.pack(side=tk.BOTTOM, fill=tk.X)
        self.status_label = ttk.Label(self.status_bar_frame, text="正在初始化...")
        self.status_label.pack(side=tk.LEFT, padx=5)

        self.notebook = ttk.Notebook(master)
        self.notebook.pack(expand=True, fill=tk.BOTH, padx=5, pady=5, before=self.status_bar_frame)

        self.json_editor_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(self.json_editor_frame, text="报纸列表管理 (piliang.json)")
        self.create_json_editor_widgets(self.json_editor_frame)

        self.query_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(self.query_frame, text="报纸查询与结果")
        self.create_query_widgets(self.query_frame)

        master.protocol("WM_DELETE_WINDOW", self.quit_app)

        self.load_initial_settings()
        self.load_newspapers_to_listbox()
        self.update_status("准备就绪")
        print("UI基础结构初始化完成。")

        self._after_id_process_scraper_output = self.master.after(100, self.process_scraper_output)

    def load_initial_settings(self):
        default_chromedriver = ""
        if platform.system() == "Darwin":
            brew_paths = ["/opt/homebrew/bin/chromedriver", "/usr/local/bin/chromedriver"]
            for path in brew_paths:
                if os.path.exists(path):
                    default_chromedriver = path
                    break
        self.chromedriver_path_var.set(default_chromedriver)
        if default_chromedriver:
            self.update_status(f"默认 ChromeDriver 路径: {default_chromedriver}")
        else:
            self.update_status("请在 文件->设置 中配置 ChromeDriver 路径, 或确保其在系统PATH/相对路径中。")
        self.output_dir_var.set("")

    def configure_chromedriver_path(self):
        current_path = self.chromedriver_path_var.get()
        filetypes = [("ChromeDriver 可执行文件", "chromedriver.exe" if platform.system() == "Windows" else "chromedriver"), ("所有文件", "*.*")]
        initial_dir_path = os.path.dirname(current_path) if current_path and os.path.exists(os.path.dirname(current_path)) else os.path.expanduser("~")
        filepath = filedialog.askopenfilename(
            title="选择 ChromeDriver 可执行文件",
            initialdir=initial_dir_path,
            filetypes=filetypes
        )
        if filepath:
            base_name = os.path.basename(filepath).lower()
            if base_name != "chromedriver" and base_name != "chromedriver.exe":
                messagebox.showwarning("注意", "选择的文件名不是 'chromedriver' 或 'chromedriver.exe'。\n请确保选择了正确的 ChromeDriver 可执行文件。")
            self.chromedriver_path_var.set(filepath)
            self.update_status(f"ChromeDriver 路径已设置为: {filepath}")

    def configure_output_dir(self):
        current_dir = self.output_dir_var.get()
        dirpath = filedialog.askdirectory(
            title="选择Excel文件默认输出目录",
            initialdir=current_dir if current_dir and os.path.isdir(current_dir) else os.path.expanduser("~")
        )
        if dirpath:
            self.output_dir_var.set(dirpath)
            self.update_status(f"Excel输出目录已设置为: {dirpath}")

    def create_json_editor_widgets(self, parent_frame):
        list_frame = ttk.Frame(parent_frame)
        list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
        ttk.Label(list_frame, text="当前报纸列表:").pack(anchor=tk.W)
        self.newspaper_listbox = tk.Listbox(list_frame, height=15, selectmode=tk.SINGLE, exportselection=False)
        self.listbox_scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.newspaper_listbox.yview)
        self.newspaper_listbox.config(yscrollcommand=self.listbox_scrollbar.set)
        self.listbox_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.newspaper_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.newspaper_listbox.bind('<<ListboxSelect>>', self.on_listbox_select)

        controls_frame = ttk.Frame(parent_frame)
        controls_frame.pack(side=tk.LEFT, fill=tk.Y)
        ttk.Label(controls_frame, text="报纸名称:").pack(anchor=tk.W, pady=(0,2))
        self.newspaper_name_entry = ttk.Entry(controls_frame, width=30)
        self.newspaper_name_entry.pack(fill=tk.X, pady=(0,10))
        self.btn_load = ttk.Button(controls_frame, text="从 piliang.json 加载/刷新", command=self.load_newspapers_to_listbox)
        self.btn_load.pack(fill=tk.X, pady=3)
        self.btn_add = ttk.Button(controls_frame, text="添加报纸", command=self.add_newspaper_to_listbox)
        self.btn_add.pack(fill=tk.X, pady=3)
        self.btn_update = ttk.Button(controls_frame, text="修改选中项", command=self.update_selected_newspaper)
        self.btn_update.pack(fill=tk.X, pady=3)
        self.btn_delete = ttk.Button(controls_frame, text="删除选中项", command=self.delete_selected_newspaper)
        self.btn_delete.pack(fill=tk.X, pady=3)
        ttk.Separator(controls_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
        self.btn_save = ttk.Button(controls_frame, text="保存更改到 piliang.json", command=self.save_listbox_to_json)
        self.btn_save.pack(fill=tk.X, pady=3)

    def create_query_widgets(self, parent_frame):
        config_frame = ttk.LabelFrame(parent_frame, text="查询配置", padding="10")
        config_frame.pack(fill=tk.X, pady=(0,10))

        mode_frame = ttk.Frame(config_frame)
        mode_frame.pack(fill=tk.X, pady=5)
        ttk.Label(mode_frame, text="查询模式:").pack(side=tk.LEFT, padx=(0,10))
        self.rb_single = ttk.Radiobutton(mode_frame, text="单个查询", variable=self.query_mode_var, value="single", command=self.toggle_query_input)
        self.rb_single.pack(side=tk.LEFT, padx=5)
        self.rb_batch = ttk.Radiobutton(mode_frame, text="批量查询 (从列表)", variable=self.query_mode_var, value="batch", command=self.toggle_query_input)
        self.rb_batch.pack(side=tk.LEFT, padx=5)

        self.single_query_entry_label = ttk.Label(config_frame, text="要查询的报纸名称:")
        self.single_query_entry = ttk.Entry(config_frame, width=50)
        self.batch_query_label = ttk.Label(config_frame, text="批量查询将使用“报纸列表管理”选项卡中的列表。")
        
        self.btn_start_query = ttk.Button(config_frame, text="开始查询", command=self.start_query)
        self.btn_start_query.pack(pady=10)
        
        self.btn_stop_query = ttk.Button(config_frame, text="中止查询", command=self.stop_query, state=tk.DISABLED)
        self.btn_stop_query.pack(pady=5)

        output_display_frame = ttk.Frame(parent_frame)
        output_display_frame.pack(fill=tk.BOTH, expand=True)

        output_log_frame = ttk.LabelFrame(output_display_frame, text="运行日志与结果", padding="10")
        output_log_frame.pack(fill=tk.BOTH, expand=True, pady=(0,5))

        self.output_text_area = scrolledtext.ScrolledText(output_log_frame, wrap=tk.WORD, height=15, state=tk.DISABLED)
        self.output_text_area.pack(fill=tk.BOTH, expand=True)
        
        excel_actions_frame = ttk.Frame(output_display_frame)
        excel_actions_frame.pack(fill=tk.X, pady=5)

        self.btn_open_output_dir = ttk.Button(excel_actions_frame, text="打开上次Excel文件所在目录", command=self.open_last_excel_dir, state=tk.DISABLED)
        self.btn_open_output_dir.pack(side=tk.LEFT, padx=5)

        self.toggle_query_input()

    def toggle_query_input(self):
        mode = self.query_mode_var.get()
        if not hasattr(self, 'btn_start_query'): return

        for widget in [self.batch_query_label, self.single_query_entry_label, self.single_query_entry]:
            if widget.winfo_ismapped():
                widget.pack_forget()

        if mode == "single":
            self.single_query_entry_label.pack(anchor=tk.W, pady=(5,0), before=self.btn_start_query)
            self.single_query_entry.pack(fill=tk.X, pady=(0,5), before=self.btn_start_query)
            self.single_query_entry.config(state=tk.NORMAL)
            self.single_query_entry.focus()
        elif mode == "batch":
            self.single_query_entry.config(state=tk.DISABLED)
            self.batch_query_label.pack(anchor=tk.W, pady=(5,0), before=self.btn_start_query)

    def start_query(self):
        print(f"DEBUG: Entering start_query. self.scraper_process is {self.scraper_process}, poll is {self.scraper_process.poll() if self.scraper_process else 'N/A'}")
        if self.scraper_process and self.scraper_process.poll() is None:
            messagebox.showwarning("查询进行中", "已有查询任务正在运行。请等待其完成后再开始新的查询，或中止当前查询。")
            return

        while not self.output_queue.empty():
            try:
                self.output_queue.get_nowait()
            except queue.Empty:
                break
        print("DEBUG: Output queue potentially cleared before new query.")

        mode = self.query_mode_var.get()
        newspaper_name_single = self.single_query_entry.get().strip()
        chromedriver_path = self.chromedriver_path_var.get()
        output_dir = self.output_dir_var.get()

        if mode == "single" and not newspaper_name_single:
            messagebox.showerror("输入错误", "单个查询模式下，报纸名称不能为空。")
            return

        if self.output_text_area.winfo_exists():
            self.output_text_area.config(state=tk.NORMAL)
            self.output_text_area.delete('1.0', tk.END)
            self.output_text_area.config(state=tk.DISABLED)

        self.update_status("查询开始...")
        self.log_to_output_area("INFO: 查询准备开始...\n")
        
        self.last_excel_filepath = None 
        if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
            self.btn_open_output_dir.config(state=tk.DISABLED)

        if hasattr(self, 'btn_start_query') and self.btn_start_query.winfo_exists():
            self.btn_start_query.config(state=tk.DISABLED)
        if hasattr(self, 'btn_stop_query') and self.btn_stop_query.winfo_exists():
            self.btn_stop_query.config(state=tk.NORMAL)
        if hasattr(self, 'btn_save') and self.btn_save.winfo_exists(): 
            self.btn_save.config(state=tk.DISABLED)

        script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "newspaper_scraper.py")
        if not os.path.exists(script_path):
            self.log_to_output_area(f"ERROR: 无法找到爬虫脚本 newspaper_scraper.py 在 {script_path}\n")
            messagebox.showerror("错误", f"无法找到爬虫脚本 newspaper_scraper.py。")
            self.query_finished()
            return
        
        python_executable_path = sys.executable
        self.log_to_output_area(f"INFO: Using Python interpreter for scraper: {python_executable_path}\n")
        
        command = [python_executable_path, "-u", script_path] 

        if mode == "single":
            command.extend(["--mode", "single", "--name", newspaper_name_single])
            self.log_to_output_area(f"INFO: 执行单个查询: {newspaper_name_single}\n")
        elif mode == "batch":
            if 'json_editor' not in globals() or not hasattr(json_editor, 'DEFAULT_JSON_PATH'):
                self.log_to_output_area(f"ERROR: json_editor模块或DEFAULT_JSON_PATH未定义。\n")
                messagebox.showerror("错误", "json_editor配置错误。")
                self.query_finished()
                return
            json_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), json_editor.DEFAULT_JSON_PATH)
            if not os.path.exists(json_file_path):
                 self.log_to_output_area(f"ERROR: 批量查询所需的 '{json_editor.DEFAULT_JSON_PATH}' 文件未找到。\n")
                 messagebox.showerror("错误", f"批量查询所需的 '{json_editor.DEFAULT_JSON_PATH}' 文件未找到。")
                 self.query_finished()
                 return
            command.extend(["--mode", "batch", "--json_file", json_file_path])
            self.log_to_output_area(f"INFO: 执行批量查询，使用JSON文件: {json_file_path}\n")

        if chromedriver_path:
            command.extend(["--chromedriver_path", chromedriver_path])
            self.log_to_output_area(f"INFO: 使用 ChromeDriver: {chromedriver_path}\n")
        else:
            self.log_to_output_area("INFO: 未指定 ChromeDriver 路径，脚本将尝试从PATH或默认相对路径查找。\n")

        if output_dir:
            command.extend(["--output_dir", output_dir])
            self.log_to_output_area(f"INFO: Excel 输出目录: {output_dir}\n")
        else:
            self.log_to_output_area(f"INFO: 未指定Excel输出目录，将保存到脚本工作目录。\n")

        self.log_to_output_area(f"COMMAND: {' '.join(command)}\n")

        thread = threading.Thread(target=self.run_scraper_script, args=(command,), daemon=True)
        thread.start()
        print("DEBUG: Scraper thread started.")

    def run_scraper_script(self, command):
        try:
            print(f"DEBUG_THREAD: Thread started for command: {' '.join(command)}")
            self.scraper_process = subprocess.Popen(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True, # Python 3.7+
                encoding='utf-8', # Be explicit
                errors='replace', # Handle potential decoding errors
                bufsize=1,  # Line buffered
                creationflags=subprocess.CREATE_NO_WINDOW if platform.system() == "Windows" else 0
            )
            print(f"DEBUG_THREAD: Subprocess Popen executed, PID: {self.scraper_process.pid if self.scraper_process else 'N/A'}")

            stdout_lines_processed = 0
            if self.scraper_process.stdout:
                print("DEBUG_THREAD: Entering stdout readline loop...")
                for line in iter(self.scraper_process.stdout.readline, ''):
                    self.output_queue.put(line)
                    stdout_lines_processed += 1
                print(f"DEBUG_THREAD: Exited stdout readline loop after {stdout_lines_processed} lines.")
                self.scraper_process.stdout.close()

            stderr_output_str = ""
            if self.scraper_process.stderr:
                print("DEBUG_THREAD: Reading stderr...")
                # Read all stderr at once after process might have finished or stdout closed
                raw_stderr = self.scraper_process.stderr.read()
                if raw_stderr: # Check if anything was read
                    if isinstance(raw_stderr, bytes):
                        try:
                            stderr_output_str = raw_stderr.decode('utf-8', errors='replace')
                        except Exception as dec_err:
                            print(f"DEBUG_THREAD_ERROR: Could not decode stderr: {dec_err}")
                            stderr_output_str = "Could not decode stderr bytes"
                    elif isinstance(raw_stderr, str):
                         stderr_output_str = raw_stderr
                    else:
                        stderr_output_str = "Unknown stderr format"
                    
                    if stderr_output_str.strip(): # Only print if there's actual content
                        print(f"DEBUG_THREAD_STDERR: {stderr_output_str.strip()}")
                self.scraper_process.stderr.close()
            
            print("DEBUG_THREAD: Waiting for subprocess to complete...")
            self.scraper_process.wait() 
            return_code = self.scraper_process.returncode
            print(f"DEBUG_THREAD: Subprocess completed with return code: {return_code}")

            if stderr_output_str: # If stderr was captured
                self.output_queue.put(f"STDERR_FINAL:\n{stderr_output_str}")

        except FileNotFoundError as e_fnf:
            error_msg = f"ERROR: Python解释器 '{command[0]}' 或脚本 '{command[2]}' 未找到。\n详情: {e_fnf}\n"
            print(f"DEBUG_THREAD_ERROR: FileNotFoundError: {error_msg}")
            self.output_queue.put(error_msg)
        except Exception as e:
            error_msg = f"ERROR: 运行爬虫脚本时发生意外错误: {e}\n"
            print(f"DEBUG_THREAD_ERROR: Exception: {error_msg}")
            traceback.print_exc()
            self.output_queue.put(error_msg)
        finally:
            print("DEBUG_THREAD: run_scraper_script finally block, putting None to queue.")
            self.output_queue.put(None)

    def process_scraper_output(self):
        try:
            while True:
                line = self.output_queue.get_nowait()
                if line is None:
                    print("DEBUG_QUEUE: Received None, calling query_finished.")
                    self.query_finished()
                    break # Exit while True, but after() will reschedule
                
                current_line_str = line
                if not isinstance(line, str):
                    try:
                        current_line_str = str(line)
                    except:
                        current_line_str = "Error: Could not convert log line to string.\n"
                
                self.log_to_output_area(current_line_str)

                if "SUCCESS: 查询结果已成功保存到文件:" in current_line_str:
                    try:
                        filepath_str = current_line_str.split("SUCCESS: 查询结果已成功保存到文件:", 1)[1].strip()
                        if os.path.exists(filepath_str):
                            self.last_excel_filepath = filepath_str
                            if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
                                self.btn_open_output_dir.config(state=tk.NORMAL)
                            self.update_status(f"Excel文件已生成: {os.path.basename(filepath_str)}")
                        else:
                            self.log_to_output_area(f"WARN: 日志报告Excel已保存，但路径 '{filepath_str}' 无效。\n")
                    except IndexError:
                        self.log_to_output_area("WARN: 无法从成功日志中解析Excel文件路径。\n")
                    except Exception as e:
                        self.log_to_output_area(f"WARN: 处理Excel保存成功日志时出错: {e}\n")
        except queue.Empty:
            pass
        
        if self.master.winfo_exists():
            self.master.after(100, self.process_scraper_output)

    def log_to_output_area(self, message):
        if hasattr(self, 'output_text_area') and self.output_text_area.winfo_exists():
            self.output_text_area.config(state=tk.NORMAL)
            self.output_text_area.insert(tk.END, message)
            self.output_text_area.see(tk.END)
            self.output_text_area.config(state=tk.DISABLED)

    def query_finished(self):
        print("DEBUG: query_finished called.")
        self.update_status("查询完成。")
        log_message_suffix = ""
        if self.scraper_process:
            return_code = self.scraper_process.returncode
            if return_code is not None:
                 log_message_suffix = f"查询子进程已结束，退出代码: {return_code}。\n"
                 if return_code != 0 and not self.last_excel_filepath:
                     if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
                         self.btn_open_output_dir.config(state=tk.DISABLED)
            else:
                 log_message_suffix = "查询过程已结束 (子进程状态未知或未完全终止)。\n"
                 if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
                     self.btn_open_output_dir.config(state=tk.DISABLED)
        else:
            log_message_suffix = "查询过程已结束 (未启动子进程或已清理)。\n"
            if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
                self.btn_open_output_dir.config(state=tk.DISABLED)

        self.log_to_output_area(f"\nINFO: {log_message_suffix}")

        if hasattr(self, 'btn_start_query') and self.btn_start_query.winfo_exists():
            self.btn_start_query.config(state=tk.NORMAL)
        if hasattr(self, 'btn_stop_query') and self.btn_stop_query.winfo_exists():
            self.btn_stop_query.config(state=tk.DISABLED)
        if hasattr(self, 'btn_save') and self.btn_save.winfo_exists():
             self.btn_save.config(state=tk.NORMAL)
        
        self.scraper_process = None
        print("DEBUG: query_finished executed, scraper_process is now:", self.scraper_process)
        
        if not self.last_excel_filepath:
            if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
                self.btn_open_output_dir.config(state=tk.DISABLED)

    def open_last_excel_dir(self):
        if self.last_excel_filepath and os.path.exists(self.last_excel_filepath):
            directory = os.path.dirname(self.last_excel_filepath)
            try:
                if platform.system() == "Windows":
                    os.startfile(directory)
                elif platform.system() == "Darwin":
                    subprocess.Popen(["open", directory])
                else:
                    subprocess.Popen(["xdg-open", directory])
                self.log_to_output_area(f"INFO: 尝试打开目录: {directory}\n")
            except Exception as e:
                self.log_to_output_area(f"ERROR: 打开目录失败: {e}\n")
                messagebox.showerror("错误", f"无法打开目录 '{directory}':\n{e}")
        else:
            messagebox.showinfo("提示", "没有找到上次成功生成的Excel文件记录，或文件已不存在。")
            if hasattr(self, 'btn_open_output_dir') and self.btn_open_output_dir.winfo_exists():
                self.btn_open_output_dir.config(state=tk.DISABLED)

    def on_listbox_select(self, event=None):
        selected_indices = self.newspaper_listbox.curselection()
        if selected_indices:
            selected_text = self.newspaper_listbox.get(selected_indices[0])
            self.newspaper_name_entry.delete(0, tk.END)
            self.newspaper_name_entry.insert(0, selected_text)
        else:
            self.newspaper_name_entry.delete(0, tk.END)

    def load_newspapers_to_listbox(self):
        if 'json_editor' not in globals():
            self.update_status("错误: json_editor 模块未加载。")
            return
        names, msg = json_editor.get_newspapers_list()
        self.newspaper_listbox.delete(0, tk.END)
        if names:
            for name in names:
                self.newspaper_listbox.insert(tk.END, name)
        self.update_status(msg)
        self.newspaper_name_entry.delete(0, tk.END)

    def add_newspaper_to_listbox(self):
        name_to_add = self.newspaper_name_entry.get().strip()
        if not name_to_add:
            messagebox.showwarning("输入错误", "报纸名称不能为空。")
            return
        current_items = self.newspaper_listbox.get(0, tk.END)
        if name_to_add in current_items:
            messagebox.showwarning("重复条目", f"报纸 '{name_to_add}' 已存在于列表中。")
            return
        self.newspaper_listbox.insert(tk.END, name_to_add)
        self.newspaper_name_entry.delete(0, tk.END)
        self.update_status(f"'{name_to_add}' 已添加到列表（未保存）。")

    def update_selected_newspaper(self):
        selected_indices = self.newspaper_listbox.curselection()
        if not selected_indices:
            messagebox.showwarning("操作错误", "请先在列表中选择一个报纸名称进行修改。")
            return
        new_name = self.newspaper_name_entry.get().strip()
        if not new_name:
            messagebox.showwarning("输入错误", "新的报纸名称不能为空。")
            return
        current_items = list(self.newspaper_listbox.get(0, tk.END))
        original_name = self.newspaper_listbox.get(selected_indices[0])
        temp_list = [item for i, item in enumerate(current_items) if i != selected_indices[0]]
        if new_name in temp_list and new_name != original_name :
             messagebox.showwarning("重复条目", f"报纸 '{new_name}' 已存在于列表中。")
             return
        self.newspaper_listbox.delete(selected_indices[0])
        self.newspaper_listbox.insert(selected_indices[0], new_name)
        self.newspaper_listbox.select_set(selected_indices[0])
        self.update_status(f"列表中的 '{original_name}' 已更新为 '{new_name}'（未保存）。")

    def delete_selected_newspaper(self):
        selected_indices = self.newspaper_listbox.curselection()
        if not selected_indices:
            messagebox.showwarning("操作错误", "请先在列表中选择一个报纸名称进行删除。")
            return
        name_to_delete = self.newspaper_listbox.get(selected_indices[0])
        self.newspaper_listbox.delete(selected_indices[0])
        self.newspaper_name_entry.delete(0, tk.END)
        self.update_status(f"'{name_to_delete}' 已从列表删除（未保存）。")

    def save_listbox_to_json(self):
        if 'json_editor' not in globals():
            self.update_status("错误: json_editor 模块未加载。")
            return
        current_newspaper_list = list(self.newspaper_listbox.get(0, tk.END))
        if not current_newspaper_list:
            if not messagebox.askyesno("确认保存空列表", "列表为空，确定要保存一个空的 piliang.json 文件吗？"):
                self.update_status("保存操作已取消。")
                return
        success, msg = json_editor.save_newspapers_from_list(current_newspaper_list)
        if success:
            messagebox.showinfo("保存成功", msg)
        else:
            messagebox.showerror("保存失败", msg)
        self.update_status(msg)

    def stop_query(self):
        if self.scraper_process and self.scraper_process.poll() is None:
            try:
                self.log_to_output_area("INFO: 尝试中止查询...\n")
                self.scraper_process.terminate() # SIGTERM
                # self.scraper_process.kill() # SIGKILL if terminate is not enough
                self.update_status("查询已中止 (等待进程响应)。")
            except Exception as e:
                self.log_to_output_area(f"ERROR: 中止查询时发生错误: {e}\n")
                messagebox.showerror("错误", f"中止查询失败: {e}")
                self.query_finished() 
        else:
            self.log_to_output_area("INFO: 当前没有正在运行的查询可以中止。\n")
            if hasattr(self, 'btn_stop_query') and self.btn_stop_query.winfo_exists():
                self.btn_stop_query.config(state=tk.DISABLED)
            if hasattr(self, 'btn_start_query') and self.btn_start_query.winfo_exists() and \
               self.btn_start_query['state'] == tk.DISABLED and self.scraper_process is None:
                self.btn_start_query.config(state=tk.NORMAL)

    def quit_app(self):
        if self.scraper_process and self.scraper_process.poll() is None:
            if messagebox.askyesno("退出确认", "查询任务仍在进行中。确定要中止查询并退出吗？"):
                self.stop_query()
                # Give a moment for the process to terminate and thread to clean up
                self.master.after(500, self._perform_destroy)
            else:
                return # Don't quit
        elif messagebox.askokcancel("退出", "确定要退出程序吗？"):
            self._perform_destroy()

    def _perform_destroy(self):
        print("应用程序退出。")
        if self._after_id_process_scraper_output:
            try:
                self.master.after_cancel(self._after_id_process_scraper_output)
                self._after_id_process_scraper_output = None 
            except tk.TclError:
                pass # Master might be already destroyed
            except Exception as e:
                print(f"Error cancelling after_id: {e}")
        
        # Ensure scraper_process is handled if somehow still active
        if self.scraper_process and self.scraper_process.poll() is None:
            print("DEBUG: Forcing termination of scraper_process during destroy.")
            try:
                self.scraper_process.terminate()
                self.scraper_process.wait(timeout=0.5) # Brief wait
                if self.scraper_process.poll() is None:
                    self.scraper_process.kill()
            except Exception as e:
                print(f"Error terminating scraper_process during destroy: {e}")

        if self.master.winfo_exists():
            self.master.destroy()

    def show_about_dialog(self):
        messagebox.showinfo("关于", "报纸信息查询工具\n版本 0.2.2\n由 AI 辅助开发")

    def update_status(self, message):
        if hasattr(self, 'status_label') and self.status_label and self.status_label.winfo_exists():
            self.status_label.config(text=message)
        else:
            print(f"Status update (label not ready or destroyed): {message}")

if __name__ == "__main__":
    if not os.path.exists("json_editor.py"):
        print("警告: 'json_editor.py' 未找到。将创建一个空文件以允许UI启动，但JSON编辑功能将受限。")
        with open("json_editor.py", "w", encoding='utf-8') as f_placeholder:
            f_placeholder.write("# Placeholder for json_editor.py\n")
            f_placeholder.write("DEFAULT_JSON_PATH = 'piliang.json'\n")
            f_placeholder.write("def get_newspapers_list(filepath=None): return [], 'Error: json_editor.py is a placeholder.'\n")
            f_placeholder.write("def save_newspapers_from_list(newspaper_list, filepath=None): return False, 'Error: json_editor.py is a placeholder.'\n")
        try:
            import json_editor # Re-import after creation
        except ImportError:
            messagebox.showerror("严重错误", "无法创建或导入 json_editor.py 占位符。")

    root = tk.Tk()
    app = NewspaperToolApp(root)
    root.mainloop()